package com.mrzhou.auth.provider;

import com.mrzhou.auth.handler.CheckHandler;
import com.mrzhou.auth.service.CloudUserDetails;
import com.mrzhou.auth.support.IntegrationAuthenticationContext;
import com.mrzhou.auth.utils.TokenUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.*;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.*;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.util.Assert;

import java.util.HashMap;
import java.util.Map;

/**
 * 自定义AuthenticationProvider
 */
@Slf4j
public class LoginAuthenticationProvider extends DaoAuthenticationProvider {

    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
    private UserCache userCache = new NullUserCache();
    private boolean forcePrincipalAsString = false;
    protected boolean hideUserNotFoundExceptions = true;
    private UserDetailsChecker preAuthenticationChecks = new LoginAuthenticationProvider.DefaultPreAuthenticationChecks();
    private UserDetailsChecker postAuthenticationChecks = new LoginAuthenticationProvider.DefaultPostAuthenticationChecks();
    private Map<String, CheckHandler> checkHandlerMap = new HashMap<>();

    public LoginAuthenticationProvider(UserDetailsService userDetailsService) {
        super();
        this.setUserDetailsService(userDetailsService);
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }
            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

    /**
     *  根据不同的登录方式来决定使用哪种登录的类型
     */
    @SneakyThrows
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if(!(userDetails instanceof CloudUserDetails)) {
            throw new BadCredentialsException("已使用【CloudUserDetails.class】作为登录成功返回的实体类对象！");
        }

        // 从ThreadLoad中获取上文的请求参数
        String authType = IntegrationAuthenticationContext.get().getAuthType();

        // 默认使用用户名密码登录
        if(StringUtils.isBlank(authType)) {
            authType = TokenUtil.AUTH_PSW;
        }

        this.checkHandlerMap.get(authType).authenticationChecks(userDetails, authentication);

        log.info("自定义密码验证！用户【{}】的登录的类型【{}】", userDetails.getUsername(), authType);
    }

    public void setCheckHandlerMap(String key, CheckHandler checkHandler) {
        this.checkHandlerMap.put(key, checkHandler);
    }

    public void setCheckHandlerMap(Map<String, CheckHandler> map) {
        this.checkHandlerMap.putAll(map);
    }

    private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
        private DefaultPreAuthenticationChecks() {
        }

        public void check(UserDetails user) {
            if (!user.isAccountNonLocked()) {
                LoginAuthenticationProvider.this.logger.debug("User account is locked");
                throw new LockedException(LoginAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"));
            } else if (!user.isEnabled()) {
                LoginAuthenticationProvider.this.logger.debug("User account is disabled");
                throw new DisabledException(LoginAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"));
            } else if (!user.isAccountNonExpired()) {
                LoginAuthenticationProvider.this.logger.debug("User account is expired");
                throw new AccountExpiredException(LoginAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"));
            }
        }
    }

    private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
        private DefaultPostAuthenticationChecks() {
        }

        public void check(UserDetails user) {
            if (!user.isCredentialsNonExpired()) {
                LoginAuthenticationProvider.this.logger.debug("User account credentials have expired");
                throw new CredentialsExpiredException(LoginAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired"));
            }
        }
    }

}
